/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include <unistd.h>
#include <rpc/types.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <errno.h>

#include "hash.h"
#include "common.h"

static HASH_TABLE_T *shashSysGroup;
static HASH_TABLE_T *shashSysUser;
static HASH_TABLE_T *shashSysIdUser;

static int setback_uid(uid_t);
static void free_group(void *);

char *
getmy_uname(void)
{
    struct passwd pwd;
    struct passwd *result;

    size_t bufsize;
    bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);

    char *buf;

    uid_t id;
    id = getuid();

    char strUserId[128];
    snprintf(strUserId, 12, "%d", id);

    struct passwd *backupUser;

    backupUser = (struct passwd *)gf_hash_find(shashSysIdUser, strUserId);
    if (NULL != backupUser) {
        buf = copy_string(backupUser->pw_name);
        return buf;
    }

    buf = (char *)calloc(bufsize, sizeof(char));
    if (NULL == buf) {
        abort();
    }

    int s;
    s = getpwuid_r(id, &pwd, buf, bufsize, &result);
    if (NULL == result) {

        if (0 != s) {
            errno = s;
        }
        FREEUP(buf);

        return NULL;
    }

    backupUser = (struct passwd *)calloc(1, sizeof(struct passwd));
    if (NULL == backupUser) {
        FREEUP(buf);
        return NULL;
    }

    backupUser->pw_uid = pwd.pw_uid;
    backupUser->pw_gid = pwd.pw_gid;
    backupUser->pw_name = copy_string(pwd.pw_name);
    backupUser->pw_passwd = copy_string(pwd.pw_passwd);
    backupUser->pw_gecos = copy_string(pwd.pw_gecos);
    backupUser->pw_dir = copy_string(pwd.pw_dir);
    backupUser->pw_shell = copy_string(pwd.pw_shell);

    if (NULL == shashSysIdUser) {
        shashSysIdUser = gf_hash_make(23);
    }

    gf_hash_install(shashSysIdUser, strUserId, (void *)backupUser);

    return buf;
}

char *
get_uname(uid_t uid)
{
    struct passwd pwd;
    struct passwd *result;

    size_t bufsize;
    bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);

    char *buf;
    buf = (char *)calloc(bufsize, sizeof(char));
    if (NULL == buf) {
        abort();
    }

    int s;
    s = getpwuid_r(uid, &pwd, buf, bufsize, &result);
    if (NULL == result) {

        if (0 != s) {
            errno = s;
        }

        return NULL;
    }

    return buf;
}

uid_t
get_uid(const char *username, uid_t *uid)
{
    if (NULL == username || '\0' == username[0]) {
        return FALSE;
    }

    size_t bufsize;
    bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);

    char *buf;
    buf = (char *)calloc(bufsize, sizeof(char));
    if (NULL == buf) {
        abort();
    }

    struct passwd pwd;
    struct passwd *result;
    int ret;
    ret = getpwnam_r(username, &pwd, buf, bufsize, &result);
    if (NULL == result) {

        if (0 != ret) {
            errno = ret;
        }

        free(buf);
        return (-1);
    }
    free(buf);

    *uid = pwd.pw_uid;

    return *uid;
}

struct passwd *
get_user_byname(const char *username)
{
    struct passwd *backupUser;

    backupUser = (struct passwd *)gf_hash_find(shashSysUser, username);
    if (NULL != backupUser) {
        return backupUser;
    }

    size_t bufsize;
    bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);

    char *buf;
    buf = (char *)calloc(bufsize, sizeof(char));
    if (NULL == buf) {
        abort();
    }

    struct passwd pwd;
    struct passwd *result;
    int ret;
    while (TRUE) {
        ret = getpwnam_r(username, &pwd, buf, bufsize, &result);
        if (NULL == result) {

            if (EINTR == ret) {
                continue;
            }

            errno = ret;

            FREEUP(buf);
            FREEUP(backupUser);
            return (NULL);
        }

        break;
    }

    backupUser = (struct passwd *)calloc(1, sizeof(struct passwd));
    if (NULL == backupUser) {
        FREEUP(buf);
        return NULL;
    }

    backupUser->pw_uid = pwd.pw_uid;
    backupUser->pw_gid = pwd.pw_gid;
    backupUser->pw_name = copy_string(pwd.pw_name);
    backupUser->pw_passwd = copy_string(pwd.pw_passwd);
    backupUser->pw_gecos = copy_string(pwd.pw_gecos);
    backupUser->pw_dir = copy_string(pwd.pw_dir);
    backupUser->pw_shell = copy_string(pwd.pw_shell);

    if (NULL == shashSysUser) {
        shashSysUser = gf_hash_make(23);
    }

    gf_hash_install(shashSysUser, backupUser->pw_name, (void *)backupUser);

    FREEUP(buf);

    return backupUser;
}

uid_t
change_uid(uid_t uid)
{
    uid_t myuid;
    int errnoSv = errno;

    if ((myuid = geteuid()) == uid) {
        return myuid;
    }

    if (myuid != 0 && uid != 0) {
        setback_uid(0);
    }

    setback_uid(uid);
    errno = errnoSv;

    return myuid;
}

struct group *
get_group_byname(const char *name)
{
    struct group *backupGrp;
    backupGrp = (struct group *)gf_hash_find(shashSysGroup, name);
    if (NULL != backupGrp) {
        return backupGrp;
    }

    size_t bufsize;
    bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);

    char *buf;
    buf = (char *)calloc(bufsize, sizeof(char));
    if (NULL == buf) {
        abort();
    }

    int ret;
    struct group grp, *grpResult = NULL;
    ret = getgrnam_r(name, &grp, buf, bufsize, &grpResult);
    if (0 != ret || NULL == grpResult) {
        if (errno == ERANGE) {
        }

        FREEUP(buf);
        return NULL;
    }

    backupGrp = (struct group *)calloc(1, sizeof(struct group));
    if (NULL == backupGrp) {
        FREEUP(buf);
        return NULL;
    }

    backupGrp->gr_gid = grpResult->gr_gid;
    backupGrp->gr_name = copy_string(grpResult->gr_name);
    backupGrp->gr_passwd = copy_string(grpResult->gr_passwd);

    unsigned int num;
    for (num=0; grpResult->gr_mem[num]; num++) ;

    backupGrp->gr_mem = (char **)calloc(num+1, sizeof(char *));
    if (NULL == backupGrp->gr_mem) {

        free_group(backupGrp);
        FREEUP(buf);
        return NULL;
    }

    for (num=0; grpResult->gr_mem[num]; num++) {
        backupGrp->gr_mem[num] = copy_string(grpResult->gr_mem[num]);
    }

    if (NULL == shashSysGroup) {
        shashSysGroup = gf_hash_make(100);
    }

    gf_hash_install(shashSysGroup, backupGrp->gr_name, (void *)backupGrp);

    FREEUP(buf);

    return backupGrp;
}

void
clean_groups(void)
{
    gf_hash_free(shashSysGroup, free_group);

    return;
}

static void
free_group(void *sysgrp)
{
    struct group *rmGrp = (struct group *)sysgrp;

    if (NULL == rmGrp) {
        return;
    }

    FREEUP(rmGrp->gr_name);
    FREEUP(rmGrp->gr_passwd);

    if (NULL != rmGrp->gr_mem) {

        unsigned int num;
        for (num=0; rmGrp->gr_mem[num]; num++) {
            FREEUP(rmGrp->gr_mem[num]);
        }

        FREEUP(rmGrp->gr_mem);
    }

    FREEUP(rmGrp);

    return;
}

static int
setback_uid(uid_t uid)
{
  if (seteuid(uid) < 0) {
      log_message(ERR, NOCLASS, "%s: Failed to change to uid <%d>: %m", __func__, uid);
      return -1;
  }

  if (uid == 0) {
      if (setreuid(0, 0) < 0) {
          log_message(ERR, NOCLASS, "%s: Failed to change to uid <%d>: %m", __func__, uid);
          return -1;
      }
  }

  return 0;
}
